⚡ 30 秒速记
- 解析检查语法,引擎再编译与执行
- 调用栈记录当前函数与返回位置
- 词法环境决定变量沿哪里查找
- 普通函数
this看调用方式,箭头函数捕获外层 - 闭包保留可达绑定,不等于整段调用永不结束
引擎执行代码时,一边按调用栈管理正在做的事,一边按词法环境查找变量。 代码会先经过解析,具体解释和编译策略由引擎决定;函数调用时进入新的执行上下文,返回时退出。变量的外层关系来自定义位置,普通函数的 this 却要看怎样调用。闭包可以让外层绑定继续可达,所以函数已经返回,相关数据仍可能被后续回调使用。
版本校准: 原理文章中的代码代表特定实现与写作时间。应用到当前项目时,应先确认浏览器、框架或工具的主版本,再区分稳定的规范语义、可变化的内部实现和项目自身约束。
在 V8 引擎中 JavaScript 代码的运行过程主要分成三个阶段
- 语法分析阶段。 该阶段会对代码进行语法分析,检查是否有语法错误(SyntaxError),如果发现语法错误,会在控制台抛出异常并终止执行
- 编译阶段。 该阶段会进行执行上下文(Execution Context)的创建,包括创建变量对象、建立作用域链、确定 this 的指向等。每进入一个不同的运行环境时,V8 引擎都会创建一个新的执行上下文。
- 执行阶段。 将编译阶段中创建的执行上下文压入调用栈,并成为正在运行的执行上下文,代码执行结束后,将其弹出调用栈
其中,语法分析阶段属于编译器通用内容,就不再赘述。前面提到的执行环境、词法环境、作用域、执行上下文等内容都是在编译和执行阶段中产生的概念。
JavaScript 在运行过程中会产生一个调用栈,调用栈遵循 LIFO(先进后出,后进先出)
# 执行上下文的创建
执行上下文的创建离不开 JavaScript 的运行环境,JavaScript 运行环境包括全局环境、函数环境和eval,其中全局环境和函数环境的创建过程如下
- 第一次载入 JavaScript 代码时,首先会创建一个全局环境。全局环境位于最外层,直到应用程序退出后(例如关闭浏览器和网页)才会被销毁
- 每个函数都有自己的运行环境,当函数被调用时,则会进入该函数的运行环境。当该环境中的代码被全部执行完毕后,该环境会被销毁。不同的函数运行环境不一样,即使是同一个函数,在被多次调用时也会创建多个不同的函数环境
在不同的运行环境中,变量和函数可访问的其他数据范围不同,环境的行为(比如创建和销毁)也有所区别。而每进入一个不同的运行环境时,JavaScript 都会创建一个新的执行上下文,该过程包括:
- 建立作用域链(Scope Chain);
- 创建变量对象(Variable Object,简称 VO);
- 确定 this 的指向。
由于建立作用域链过程中会涉及变量对象的概念,因此我们先来看看变量对象的创建,再看建立作用域链和确定 this 的指向
# 创建变量对象
什么是变量对象呢?每个执行上下文都会有一个关联的变量对象,该对象上会保存这个上下文中定义的所有变量和函数